'''
Created on Jun 8, 2011

@author: kiira
'''
import Graphics, os, Util, pygame, math, random, operator, time

from xml.dom import minidom
from copy import copy
from collections import deque
from Entity import Entity, Char



''' Global variable used principally in load_entity_sprite. It contains descriptions/sprites of the actions of the diferent entities.'''
actions = {}
''' Global variable used principally in load_entity_sprite. It contains descriptions/sprites of head attachments for diferent entities. '''
heads = {}

def init():
    ''' This method initializes the Graphics module to be able to retrieve RTO sprites correctly. '''
    #get the initialization document from the data folder
    document = minidom.parse( os.path.join( 'data', 'init.xml' ) )
     
    #look for the loaders and put them into a list
    loaders = document.getElementsByTagName( 'loaders' )[0].getElementsByTagName( 'loader' )
    
    #register the loaders into the Graphics module
    for loader in loaders:
        Graphics.register_loader( os.path.normcase( loader.firstChild.data ), loader.getAttribute( 'name' ), loader.getAttribute( 'type' ) )
        
    #initialize the base actions
    global actions
    #get the base action nodes
    bases = document.getElementsByTagName( 'actions' )[0].getElementsByTagName( 'base' )
        
    #load and register the actions
    for base in bases:
        actions[base.getAttribute( 'name' )] = _parse_actions( os.path.normcase( base.firstChild.data ) )
          
    #initialize the base head positions
    global heads
    #get the base head pos nodes
    bases = document.getElementsByTagName( 'head_pos' )[0].getElementsByTagName( 'base' )
    
    #load and register the head_pos
    for base in bases:
        heads[base.getAttribute( 'name' )] = _parse_head_pos( os.path.normcase( base.firstChild.data ) )
    
          
def _parse_actions( action_file ):
    '''Parses an xml file, extracting it's action nodes and returning the names in AttrDict format '''
    try:
        #parse the xml file and obtain all action nodes
        xml_doc = minidom.parse( action_file )
        action_nodes = xml_doc.firstChild.getElementsByTagName( "action" )

        #put all the action names into a list, adding for each node, it's own directions
        actions = Util.AttrDict()
        for node in action_nodes:
            action_name = node.attributes["name"].value
            actions[action_name] = None

            direction_nodes = node.getElementsByTagName( "direction" )
            for dnode in direction_nodes:
                actions[action_name][dnode.attributes["name"].value] = node.getAttribute( 'anchor' )

        return actions

    except:
        print 'Invalid xml file.'
        return []
    
def _parse_head_pos( head_pos_file ):
    '''
    Parses an xml file, extracting the head_pos and adding them into a dictionary that references each frame of an action
    with a given head image and position in reference to the sprite.
    '''
    try:
        #parse the xml and obtain the action nodes
        xml_doc = minidom.parse( head_pos_file )
        action_nodes = xml_doc.firstChild.getElementsByTagName( 'action' )
                
        #iterate over the action, creating a new entry in the dictionary for each action, and inside it, another
        #dictionary that references each frame of the action with it's given head attach information
        head_pos = {}
        
        for action in action_nodes:
            #get the action name and create the entry
            action_name = action.getAttribute( 'name' )
            head_pos[action_name] = []
            
            #get the frame nodes and iterate over them
            frame_nodes = action.getElementsByTagName( 'frame' )
            
            for frame in frame_nodes:
                #get the data
                frame_num = int( frame.getAttribute( 'number' ) )
                image = frame.getElementsByTagName( 'image' )[0].firstChild.data
                xPos = int( frame.getElementsByTagName( 'xpos' )[0].firstChild.data )
                yPos = int( frame.getElementsByTagName( 'ypos' )[0].firstChild.data )
                front = frame.attributes.get( 'front', 'True' ) == 'True'                
                                
                #create the entry
                head_pos[action_name].append( ( image, frame_num, ( xPos, yPos ), front ) )
                
        return head_pos
        
    except:
        print 'Invalid xml file.'
        return []              
                 
def load_entity_sprite( entity_type, monster_name=None, char_genre=None, char_class=None, initial_pos=( 0, 0 ) ):
    ''' 
    Loads an entity sprite using the base action list given loaded initially, plus any other action defined in
    the entity directory.
    '''
    global actions
    global heads
    
    #create the entity key for the actions dict
    entity_key = entity_type + ( monster_name if monster_name else '' ) + ( char_genre if char_genre else '' ) + ( char_class if char_class else '' )
    
    if entity_key in actions.keys():
        #if the entity was already loaded, no need to reload
        entity_sprite = CharSprite( actions[entity_key].copy(), entity_type, char_genre, char_class, heads[entity_key].copy(), initial_pos )
    elif entity_type == Entity.ENTITY_TYPES.char:
        #check that a char_genre and class were defined
        if not ( char_genre and char_class ):
            raise TypeError( 'Neither char_genre or char_class can be None.' )
        
        #generate the entity resources path
        path = os.path.join( 'data', 'entities', entity_type, char_genre, char_class, 'body' )
        
        #check that the path exists
        if not os.path.exists( path ):
            raise RuntimeWarning( "The path of the entitie's resources (" + path + ") doesn't exists." )
        
        #get a copy of the base actions attrdict
        new_actions = actions[entity_type + '_' + char_genre].copy()
          
        #check if an actions xml file exists
        if 'actions.xml' in os.listdir( path ):
            #parse the file and retrieve the action nodes
            document = minidom.parse( os.path.join( path, 'actions.xml' ) )
            action_nodes = document.firstChild.getElementsByTagName( "action" )
            
            #iterate over the nodes to update the actions
            for node in action_nodes:
                #get the action name
                action_name = node.getAttribute( 'name' )
                
                #add the action
                new_actions[action_name] = None
                
                #retrieve the direction nodes
                direction_nodes = node.getElementsByTagName( 'direction' )    
                
                #iterate over the directions
                for dnode in direction_nodes:                    
                    #add the directions on the action
                    new_actions[action_name][dnode.getAttribute( 'name' )] = node.getAttribute( 'anchor' )
                
        #generate the sprites for every action        
        #aux variables
        loader_name = entity_type
        delay = 110
        scale = ( 0.8, 0.8 )
        dir_tree = [ char_genre, char_class, 'body']
        
        for action in new_actions.keys():
            for direction in new_actions[action].keys():
                resource = action + '_' + direction
                
                anchor = new_actions[action][direction]
                                
                new_actions[action][direction] = Graphics.load( resource, loader_name, dir_tree, anchor=anchor, delay=delay, scale=scale )
                        
        #create the sprite
        entity_sprite = CharSprite( new_actions, entity_type, char_genre, char_class, char_class, initial_pos )         
        
        #add the actions to the actions dictionary
        actions[entity_key] = new_actions.copy()
        
        #add the heads to the heads dictionary
        heads[entity_key] = entity_sprite._head.copy()           
            
    elif entity_type == Entity.ENTITY_TYPES.monster:
        pass
    else:
        raise TypeError( 'Entity type undefined.' )

    return entity_sprite

class EntitySprite:
    '''
    Represents an entity sprite, having methods to manipulate the graphic representation of an entity action.
    Also mantains useful information about the sprite state, attachs and other important stuff to be able to use the sprite
    into the game enviroment.
    '''
    
    def __init__( self, animation_list, entity_type, initial_pos=( 0, 0 ), speed=800 ):
        '''
        Initializes an EntitySprite with the given actions.
        
        Argument Keywords:
        animation_list -- Dict that contains as key values the names of the actions that the sprite can perform, and as values
        the actual SpriteAnimation that represents that action.
        initial_pos -- Initial position of the sprite, referenced from the 0,0 of the surface where it will be rendered
        '''
        self._animation_list = animation_list           #Dict of action_names -> directions -> sprites
        self.entity_type = entity_type                  #type of the entity
        self.pos = initial_pos                          #position of the sprite
        self.current_action = None                      #current action performed by the sprite
        
        self._action_queue = deque()                    #queue where actions can be stashed with it correspondant "play time" (frames)
        self._current_action_counter = 0                #frame counter for the current action
        self._last_time = 0                             #time of the last call to render
        
        self.speed = speed                              #speed factor form where the mov_speed is calculated
        self._mov_speed = ( 0, 0 )                      #movement speed of the sprite
        self._mov_dist = ( 0, 0 )                       #distance vector that indicates how much movement is left to do
        self._facing_dir = None                         #direction towards the char is facing
        self._mov_queue = deque()                       #movement queue
               
    def __getattr__( self, attr ):
        ''' Modification to the __getattr__ method to add the posibility to access to action names directly from an entity. '''
        if attr == 'action':
            return self._animation_list
        else:
            return object.__getattribute__( self, attr )
    
    def move_to( self, pos ):
        ''' 
        Makes the sprite to "move" from a point in the plane to another. 
        
        Argument Keywords:
        pos -- Position where to move to.
        
        '''              
        #append the movement and the action to it's correspondant queues
        self._mov_queue.append( pos )
        self._action_queue.append( self._next_move )
        
    def _move_to( self, pos ):
        ''' 
        Makes the current moving happen, changing the data of the entity movement.
        
        Argument Keywords:
        pos -- Position where to move to.
        '''
        #get the distance vector and calculate the distance
        dist_vec = tuple( map( operator.sub, pos, self.pos ) )
        distance = math.sqrt( math.pow( dist_vec[0], 2 ) + math.pow( dist_vec[1], 2 ) )
        
        #if distance is 0, there is no movement
        if not distance:
            return
        
        #calculate the distance in absolute value
        self._mov_dist = tuple( map( abs, dist_vec ) )  
                       
        #redefine the velocity to go acordingly the direction of the movement
        speedX = dist_vec[0] / distance * self.speed
        speedY = dist_vec[1] / distance * self.speed
        
        self._mov_speed = ( speedX, speedY )     
        
        #determine the action to perform during movement and overwrite current action
        self._facing_dir = EntitySprite._determine_dir( dist_vec )
        self._change_action( ( ( 'walk', self._facing_dir ), 0 ) )
        
    def _next_move( self ):
        ''' Generates the next movement. '''
        self._move_to( self._mov_queue.popleft() )
        
    def atk( self ):
        ''' Generates an atk action for the entity. '''
        raise 'Pleas overwrite this method.'    
            
    def perform_action( self, action, frame_counter=None ):
        '''
        Sets the action to perform next.
        
        Key Arguments:
        action -- Tuple containing the action and direction of the sprite to use next.
        frame_counter -- Indicates for how many frames the action should be played before turning it out.
        
        Note: By default, if you don't set a frame counter, it will be setted to the frame count of the sprite being set. 
        Also, if the last action setted's frame count didn't zeroed yet, the action will be queued, and played AFTER the 
        current action, and all the actions that were queued before it.
        '''                
        #add this action to the queue
        self._action_queue.append( ( action, frame_counter ) )
            
    def _change_action( self, action ):
        ''' Handles the replacement of the current action for a new action. '''
        #if the action is a function callback, call it and return
        if hasattr( action, '__call__' ):
            action()
            return
               
        #stop & reset previous action
        if self.current_action:
            self.current_action.stop()
            self.current_action.reset()
        
        #set and start new action
        if action[0]:  
            #unpack action
            act, facing_dir = action[0]
            
            #if direction is not determined, use the current facing dir
            if not facing_dir:
                facing_dir = self._facing_dir
                
            self.current_action = self._animation_list[act][facing_dir]
            self.current_action.play()
                    
        #change the frame_counter to default if necesary        
        frame_counter = action[1]
        
        if frame_counter == None:
            frame_counter = self._animation_list[act][facing_dir].length()
            
        #transform the frame_counter into miliseconds
        if act:
            frame_counter *= self._animation_list[act][facing_dir].get_delay() 
        else:
            frame_counter *= .008
        
        #st the action counter
        self._current_action_counter = frame_counter
        
        #set the facing dir
        self._facing_dir = facing_dir
        
    def _update( self ):
        ''' Updates the action counter and changes the action if necesary. Also updates the sprite movement. '''
        #get the time
        current_time = time.time()
        passed_time = current_time - self._last_time
        
        #action performing
        #substract the time from the counter
        if self._current_action_counter:
            self._current_action_counter -= passed_time 
            
            if self._current_action_counter < 0:
                self._current_action_counter = 0
            
        
        speed_fraction = 10 #indicates the fractioning of the speed ( ms, s, etc )
        
        #change the current action if the queue haves action and the sprite is not moving
        if not self._current_action_counter and len( self._action_queue ) and self._mov_dist == ( 0, 0 ):
            self._change_action( self._action_queue.popleft() )
                    
        elif self._mov_dist != ( 0, 0 ): #check if there is movement to be done
            #move the sprite and actualize the attributes referred to movement
            if self._mov_dist[0] != 0:
                movX = self._mov_speed[0] * passed_time / speed_fraction 
                distX = self._mov_dist[0] - abs( movX )
                
                if distX < 0:
                    movX += distX * cmp( movX, 0 )
                    distX = 0
            else:
                distX = 0
                movX = 0
            
            if self._mov_dist[1] != 0:
                movY = self._mov_speed[1] * passed_time / speed_fraction 
                distY = self._mov_dist[1] - abs( movY )
                
                if distY < 0:
                    movY += distY * cmp( movY, 0 )
                    distY = 0
            else:
                distY = 0
                movY = 0
            
            self.pos = tuple( map( operator.add, self.pos, ( movX, movY ) ) )
            self._mov_dist = ( distX, distY )            
            
            #if mov became 0...
            if self._mov_dist == ( 0, 0 ):               
                #change the action to 'stand'
                self._change_action( ( ( 'stand', self._facing_dir ), 0 ) )                     
            
        #set the time of the last render call
        self._last_time = current_time
                            
    def render( self, surface ):    
        ''' Render the current action into a surface and update the sprite status. '''   
        #update the entity sprite
        self._update() 
        
        #if there's a current action, render
        if self.current_action:
            fixing_rate = tuple( map( operator.div, self.current_action.get_size(), ( 2, 1.3 ) ) )
            rendering_pos = tuple( map( operator.sub, self.pos, fixing_rate ) )
        
            self.current_action.render( surface, rendering_pos )
            
                
    def test_all( self ):
        for action in self._animation_list.keys():
            for direction in self._animation_list[action]:
                self.perform_action( ( action, direction ), 10 )
            
    @staticmethod    
    def _determine_dir( dist ):
        #calculate the angle fo the dist vector
        angle = math.degrees( math.atan( abs( float( dist[1] ) / dist[0] ) )if dist[0] != 0 else math.pi / 2 )
        
        #determine the quadrant and set the direction accordingly to the angle
        if dist[0] >= 0 and dist[1] >= 0:
            if angle < 15:
                direction = 'right'
            elif angle < 45:
                direction = 'rightbot'
            else:
                direction = 'bot'
        elif dist[0] <= 0 and dist[1] >= 0:
            if angle < 15:
                direction = 'left'
            elif angle < 45:
                direction = 'leftbot'
            else:
                direction = 'bot'
        elif dist[0] <= 0 and dist[1] <= 0:
            if angle < 15:
                direction = 'left'
            elif angle < 45:
                direction = 'lefttop'
            else:
                direction = 'top'
        elif dist[0] >= 0 and dist[1] <= 0:
            if angle < 15:
                direction = 'right'
            elif angle < 45:
                direction = 'righttop'
            else:
                direction = 'top'
                
        return direction        
        
class CharSprite( EntitySprite ):
    ''' Represents a character sprite. '''
    
    def __init__( self, animation_list, entity_type, char_genre, char_class, head, initial_pos=( 0, 0 ) ):
        '''
        Initializes an CharSprite with the given actions.
        
        Argument Keywords:
        animation_list -- Dict that contains as key values the names of the actions that the sprite can perform, and as values
        the actual SpriteAnimation that represents that action.
        head -- Dict containing the head attached to the body sprite. Each perspective/s of the head are attached to certain action/
        perspectives of the body sprite, so each action of the body sprite is also a key in the head dict.
        initial_pos -- Initial position of the sprite, referenced from the 0,0 of the surface where it will be rendered
        '''
        #init superclass
        EntitySprite.__init__( self, animation_list, entity_type, initial_pos )
        
        self.char_genre = char_genre                    #character genre
        self.char_class = char_class                    #character class
        
        
        self._weapon = dict()                           #Dict of action_names -> weapon
        self._hat = dict()                              #Dict of action_names -> hat
        self._misc = dict()                             #Dict containing other dicts action_names -> something this last dict is for
                                                        #use into anything that doesn't fall into the other categories.
        self._head = None
                                               
        if isinstance( head, str ):
            self.change_head( head )
        else:
            self._head = head                           #Dict of action_names -> attachments
                                                        
    #TODO: Define the equip method
    #TODO: Define equip_weapon
    #TODO: Define equip_hat
    #TODO: Define the attach_misc method     
    
    def change_head( self, head_name ):
        #create the path
        path = os.path.join( 'data', 'entities', self.entity_type, self.char_genre, 'heads', head_name )
        
        #check if the path exists
        if os.path.exists( path ):
            #if so, deattach current head (if exists)
            if self._head:
                for action in self._head.keys():
                    action_name, direction = action.split( '_' )
                
                    for frame in self._head[action].keys():
                        self._animation_list[action_name][direction].deattach( self._head[action][frame] )
            
            #get a copy of the base head dict...
            global heads
            new_heads = copy( heads[self.entity_type + '_' + self.char_genre] )
          
            #and check if an actions xml file exists to expand the base dict
            if 'head_positions.xml' in os.listdir( path ):
                new_heads.update( _parse_head_pos( os.path.join( path, 'head_positions.xml' ) ) )
             
              
            #generate and attach the new head images
            head_images = {}
            loader_name = self.entity_type + '_images'
            dir_tree = [self.char_genre, 'heads', head_name] 
            scale = ( 0.8, 0.8 )
        
            for action in new_heads.keys():
                #get action name and direction
                action_name, direction = action.split( '_' )
            
                #create the list of head images for the given action
                head_images[action] = {}
            
                for attach in new_heads[action]:
                    #disenvolve attach
                    image, frame_num, pos, front = attach
                               
                    #load the image
                    image = Graphics.load( image, loader_name, dir_tree, scale=scale )
                    
                    #modify the pos for the image size
                    half_x = tuple( map( operator.div, image.get_size(), ( 2, 1 ) ) )
                    pos = tuple( map( operator.sub, pos, half_x ) )
                
                    #attach the image to the sprite
                    self._animation_list[action_name][direction].attach( image, frame_num, pos, front )
                
                    #add the head images to the dictionary
                    head_images[action][frame_num] = image
                    
                    #change the head dictionary
                    self._head = head_images 
                    
    def atk( self, atk_type=1, facing_dir=None ):
        '''
        Generates an 'attack' action.
        
        Argument Keywords:
        atk_type -- Type of attack of the char.
        facing_dir -- Facing direction of the atk.
        '''                    
        #queue the correspondant atk action
        self.perform_action( ( 'atk' + str( atk_type ), facing_dir ) )    

class Tile:
    ''' This class is RTO-specific. It represents a tile of a given map and it's attributes. '''
    def __init__( self, center, diagonal, texture, ground_elevation=0, leftside_textures=None, rightside_textures=None, fix_center=False ):
        '''
        Initializes a tile with a given texture and processes elevated tiles with its respectives textures to give a 3D look throught
        isometric perspective.
        
        Argument Keywords:
        center -- center of the tile. For tiles with ground_elevation different from 0, this center should be given for the position
        the tile WOULD have if it weren't elevated. This initializer is in charge of reallocating the center accordingly.
        diagonal -- size of the Vertical diagonal of the tile. The Horizontal diagonal is calculated from it.
        texture -- texture for the surface of the tile.
        ground_elevation -- elevation of the tile. Can be any integer, including 0. Default value -> 0
        leftside_textures -- textures of the leftsides of a elevated tile in a list. If you use a non-zero ground_elevation, you should set this.
        rightside_textures -- textures of the rightsides of a elevated tile in a list. If you use a non-zero ground_elevation, you should set this.
        fix_center -- if you set a non-zero ground elevation, you can check this argument to True if you want the class to automatically reallocate 
        the center of the tile for correct rendering of elevation. If you let this parameter with False, you should probably call the function
        fix_center() manually at some point.
        
        Note: the constructor does a small coherency check with the ground_elevation and the length of the side textures lists. If any of the three
        is different from the others, or all are different betwen them, then the elevation and the lenght of the lists are seted to the min of the 
        three of them, thus eliminating any extra texture or ground_level.
        '''
        #check the consistency between ground_elevation and the sides_textures
        if ground_elevation != 0 and ( ground_elevation != len( leftside_textures ) or ground_elevation != len ( rightside_textures ) ):
            ground_elevation = min( ground_elevation, len( leftside_textures ), len ( rightside_textures ) ) 
            
            if len( leftside_textures ) != ground_elevation:
                del( leftside_textures[ground_elevation:] )
            if len( rightside_textures ) != ground_elevation:
                del( rightside_textures[ground_elevation:] ) 

        
        self.center = center                                    #tile center for rendering
        self._diagonalY = diagonal                               #tile vertical diagonal
        self._diagonalX = int( diagonal / 0.57 )     #tile horizontal diagonal
        self._ground_elevation = ground_elevation                #tile elevation
                
        #load and set the surface texture
        texture = pygame.transform.rotate( texture, 45 )
        self._texture = pygame.transform.smoothscale( texture, ( int( self._diagonalX ), int( self._diagonalY ) ) )
        
        #if the tile haves elevation, process the side textures
        if ground_elevation > 0:
            self._rightside_textures = []                        #list of textures for the right side of the elevated tile
            self._leftside_textures = []                         #list of textures for the left side of the elevated tile
            
            #auxiliary bounding rect used to crop the side textures after re-shaping            
            bound_rect = pygame.rect.Rect( 0.26 * self._diagonalY , 0.30 * self._diagonalY, 0.5 * self._diagonalX, self._diagonalY )
            
            #iterate over the tile levels
            for i in xrange( 0, ground_elevation ):
                #if there is a designed texture for the rightside of this level, process it
                if rightside_textures[i]:
                    #re-shaping
                    rightside_textures[i] = pygame.transform.smoothscale( rightside_textures[i], ( rightside_textures[i].get_width() , rightside_textures[i].get_height() / 2 ) )
                    rightside_textures[i] = pygame.transform.rotate( rightside_textures[i], -45 )
                    rightside_textures[i] = pygame.transform.smoothscale( rightside_textures[i], ( int ( self._diagonalX * 0.8 ) , int( self._diagonalY * 0.8 ) ) )
                    rightside_textures[i] = pygame.transform.rotate ( rightside_textures[i], 60 )               
                
                    #cropping
                    rightside_textures[i] = rightside_textures[i].subsurface( bound_rect )
                
                #append the texture
                self._rightside_textures.append ( rightside_textures[i] )
                
                #if there is a designed texture for the leftside of this level, process it
                if leftside_textures[i]:
                    #re-shaping
                    leftside_textures[i] = pygame.transform.smoothscale( leftside_textures[i], ( leftside_textures[i].get_width(), leftside_textures[i].get_height() / 2 ) )
                    leftside_textures[i] = pygame.transform.rotate( leftside_textures[i], 45 )
                    leftside_textures[i] = pygame.transform.smoothscale( leftside_textures[i], ( int ( self._diagonalX * 0.8 ), int( self._diagonalY * 0.8 ) ) )
                    leftside_textures[i] = pygame.transform.rotate ( leftside_textures[i], -60 )
                
                    #cropping
                    leftside_textures[i] = leftside_textures[i].subsurface( bound_rect )
                
                #append the texture
                self._leftside_textures.append ( leftside_textures[i] )      
             
            #if the fix_center flag is on, fix the tile center for the given elevation   
            if fix_center:
                self.fix_center()
              
    def fix_center( self ):
        ''' 
        Reallocates the center of the tile acordingly of the ground_elevation. You should call this function after changing
        the tile center, if this file is over or under ground_level 0.
        '''
        self.center = ( self.center[0], self.center[1] - self._diagonalY / 2 * self._ground_elevation )
        
    def render( self, surface ): 
        ''' Render the tile into the given surface. '''      
        if self._ground_elevation > 0:
            for i in xrange( 0, self._ground_elevation ):
                if self._leftside_textures[i]:
                    surface.blit( self._leftside_textures[i], ( self.center[0] - self._diagonalX / 2 + 1, self.center[1] + self._diagonalY * i / 2 ) )
                if self._rightside_textures[i]:
                    surface.blit( self._rightside_textures[i], ( self.center[0], self.center[1] + self._diagonalY * i / 2 ) )           
        
        surface.blit( self._texture, ( self.center[0] - self._diagonalX / 2 , self.center[1] - self._diagonalY / 2 ) ) 

class Map: 
    ''' This class is RTO-specific. It represents collection of tiles that conforms a RTO map. '''
    def __init__ ( self, map_file, ref, diagonal=50 ):
        '''
        Initializes a map throught a xml specificaly formatted to be loaded as a map for this class.
        
        Argument Keywords:
        map_file -- xml file to be loaded.
        ref -- reference point (tuple) from where the map will be render. This point represents the top corner of the isometric map.
        diagonal -- size of the tile diagonal. Default -> 50
        '''
        #parse the xml file
        map_path = os.path.join( 'data', 'maps', map_file )
        document = minidom.parse( map_path )
        
        #obtain the map node and the tile nodes
        map = document.getElementsByTagName( 'map' )[0]
        tile_nodes = map.getElementsByTagName( 'tile' )
 
        #auxiliary tile list for initial loading and sorting
        tiles = []
        
        #iterate throught the xml tile nodes
        for tile_node in tile_nodes:
            #obtain the position in the map gride and the principal texture
            xpos = int ( tile_node.attributes['xpos'].value )
            ypos = int ( tile_node.attributes['ypos'].value )
            
            graphic = Graphics.load( tile_node.attributes['texture'].value, loader_name="textures" ) 
            
            if 'flip' in tile_node.attributes.keys() and int( tile_node.getAttribute( 'flip' ) ) != -1 :
                texture = graphic[int( tile_node.getAttribute( 'flip' ) )]
            else:
                texture = graphic[random.randint( 0, len( graphic ) - 1 )] 
                
            
            #if the ground_level attribute is dedsignated for the tile and it's different to 0, process the side textures
            if 'ground_level' in tile_node.attributes.keys() and tile_node.attributes['ground_level'] != 0:
                #obtain the elevation
                ground_level = int ( tile_node.attributes['ground_level'].value )
                
                #retrieve the left and right side texture nodes
                leftside_nodes = tile_node.getElementsByTagName( 'leftside_texture' )[0].getElementsByTagName( 'texture' )
                rightside_nodes = tile_node.getElementsByTagName( 'rightside_texture' )[0].getElementsByTagName( 'texture' )
                
                #sort the nodes 
                leftside_nodes.sort( key=lambda node: int( node.attributes['level'].value ), reverse=True )
                rightside_nodes.sort( key=lambda node: int( node.attributes['level'].value ), reverse=True )
                
                #auxiliary lists of textures
                leftside_textures = []
                rightside_textures = []
                
                #load the textures
                for i in xrange( 0, ground_level ):
                    if leftside_nodes[i].firstChild.data != 'None':
                        graphic = Graphics.load( leftside_nodes[i].firstChild.data, loader_name='textures' )
                        
                        if 'flip' in leftside_nodes[i].attributes.keys() and int( leftside_nodes[i].getAttribute( 'flip' ) ) != -1 :
                            leftside_textures.append( graphic[int( leftside_nodes[i].getAttribute( 'flip' ) )] )
                        else:
                            leftside_textures.append( graphic[random.randint( 0, len( graphic ) - 1 )] )
                            
                    else:
                        leftside_textures.append( None )
                        
                    if rightside_nodes[i].firstChild.data != 'None':
                        graphic = Graphics.load( rightside_nodes[i].firstChild.data, loader_name='textures' )
                        
                        if 'flip' in rightside_nodes[i].attributes.keys() and int( rightside_nodes[i].getAttribute( 'flip' ) ) != -1 :
                            rightside_textures.append( graphic[int( rightside_nodes[i].getAttribute( 'flip' ) )] )
                        else:
                            rightside_textures.append( graphic[random.randint( 0, len( graphic ) - 1 )] )
                            
                    else:
                        rightside_textures.append( None )
                
                #create the tile
                tiles.append( Tile ( ( xpos, ypos ), diagonal, texture, ground_level, leftside_textures, rightside_textures ) )      

            else:
                #create the tile
                tiles.append( Tile ( ( xpos, ypos ), diagonal, texture ) )
 
        #sort the tiles accordingly to each ones grid location
        tiles.sort( key=lambda tile: ( tile.center[0], tile.center[1] ) )    
          
        #initialize the map list   
        self.map = []
        
        #retrieve the row count and the column count
        row_count = int( map.attributes['height'].value )
        column_count = int( map.attributes['width'].value )
        
        #obtain the diagonals for tile positioning      
        diagonalX = tiles[0]._diagonalX / 2
        diagonalY = tiles[0]._diagonalY / 2  
        
        #iterate throught the row and columns of the map, setting up the tiles, resetting the centers and finally
        #appending the tiles to the map
        for row in xrange( 0, row_count ):
            wref = ref[0] - row * diagonalX 
            href = ref[1] + ( 1 + row ) * diagonalY 
            
            self.map.append( [] )
            
            for column in xrange( 0, column_count ):
                tiles[row * column_count + column].center = ( wref + column * diagonalX, href + column * diagonalY )
                tiles[row * column_count + column].fix_center()
                
                self.map[row].append( tiles[row * column_count + column] )
        
                
    def render( self, surface ):
        ''' Renders the map into the given surface. More precisely, iterate throught the tiles, rendering them. '''
        for row in xrange( 0, len( self.map ) ):
            for column in xrange( 0, len( self.map[row] ) ):
                self.map[row][column].render( surface )

    
if __name__ == '__main__':
    from pygame.locals import *
    
    init()
        
    pygame.init()
    
    reloj = pygame.time.Clock()
    
    pantalla = pygame.display.set_mode( ( 800, 600 ) )
   
    mapa = Map( "test2.xml", ( 500, 50 ), diagonal=50 )        
       
    entity = load_entity_sprite( Entity.ENTITY_TYPES.char, char_genre=Char.CHAR_GENRES.male,
                                 char_class=Char.CHAR_CLASSES.novice, initial_pos=mapa.map[9][0].center )
    entity2 = load_entity_sprite( Entity.ENTITY_TYPES.char, char_genre=Char.CHAR_GENRES.male,
                                  char_class=Char.CHAR_CLASSES.novice, initial_pos=mapa.map[3][3].center )
    entity3 = load_entity_sprite( Entity.ENTITY_TYPES.char, char_genre=Char.CHAR_GENRES.male,
                                  char_class=Char.CHAR_CLASSES.novice, initial_pos=mapa.map[4][3].center )
    entity4 = load_entity_sprite( Entity.ENTITY_TYPES.char, char_genre=Char.CHAR_GENRES.male,
                                  char_class=Char.CHAR_CLASSES.novice, initial_pos=mapa.map[3][4].center )
    
    entity2.change_head( Char.CHAR_CLASSES.hunter )
    entity4.change_head( Char.CHAR_CLASSES.hunter )

    entity.perform_action( entity.action.battle.leftbot, 15 )   
    entity.perform_action( entity.action.onhit.leftbot )
    entity.perform_action( entity.action.stand.leftbot, 30 )
    entity.perform_action( entity.action.walk.leftbot, 15 )
    entity.perform_action( entity.action.onhit.leftbot )
    entity.perform_action( entity.action.dying.leftbot )
    entity.perform_action( entity.action.dead.leftbot )

    entity2.perform_action( entity2.action.atk1.lefttop, 30 )
    entity2.perform_action( entity2.action.stand.lefttop, 5 )
    entity2.perform_action( entity2.action.stand.left, 5 )
    entity2.perform_action( entity2.action.stand.leftbot, 5 )
    entity2.perform_action( entity2.action.walk.leftbot )
    
    entity3.test_all()
    entity4.test_all()
       
            
    while True:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                exit()
                
            if event.type == MOUSEBUTTONDOWN and event.button == 1:
                entity2.move_to( pygame.mouse.get_pos() )
            elif event.type == MOUSEBUTTONDOWN and event.button == 3:
                entity2.atk( 1 )
                
        
        pantalla.fill( ( 255, 255, 255 ) )    
        mapa.render( pantalla )
        entity.render( pantalla ) 
        entity2.render( pantalla )
        entity3.render( pantalla ) 
        entity4.render( pantalla )
        pygame.display.flip()
        
        reloj.tick( 60 )
    
